package com.onesports.intelligent.k12.polarlight.service.schedulejob;

import static com.onesports.intelligent.k12.polarlight.common.constant.Constants.NOT_EXPIRED;
import static com.onesports.intelligent.k12.polarlight.common.utils.DateUtils.HH_MM;
import static com.onesports.intelligent.k12.polarlight.common.utils.DateUtils.YYYY_MM_DD_HH_MM;
import static com.onesports.intelligent.k12.polarlight.common.utils.DateUtils.date2LocalDateTime;
import static com.onesports.intelligent.k12.polarlight.common.utils.DateUtils.getLocalDateTimeStr;
import static com.onesports.intelligent.k12.polarlight.common.utils.DateUtils.parseLocalDateTime;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.onesports.framework.kit.common.util.BeanUtils;
import com.onesports.framework.kit.common.util.ParamUtils;
import com.onesports.framework.kit.common.util.TimeUtils;
import com.onesports.intelligent.k12.polarlight.annotation.DistributedLock;
import com.onesports.intelligent.k12.polarlight.common.constant.Constants;
import com.onesports.intelligent.k12.polarlight.common.domain.entity.BaseEntity;
import com.onesports.intelligent.k12.polarlight.common.utils.DateUtils;
import com.onesports.intelligent.k12.polarlight.constant.BizConstants;
import com.onesports.intelligent.k12.polarlight.constant.LockConstants;
import com.onesports.intelligent.k12.polarlight.domain.dto.course.DissolutionClassDTO;
import com.onesports.intelligent.k12.polarlight.domain.dto.organization.OrgDisabledDTO;
import com.onesports.intelligent.k12.polarlight.domain.dto.student.StuSpellClassDTO;
import com.onesports.intelligent.k12.polarlight.domain.entity.course.ClassInfo;
import com.onesports.intelligent.k12.polarlight.domain.entity.course.ClassStudent;
import com.onesports.intelligent.k12.polarlight.domain.entity.course.ClassStudentScheduleDetail;
import com.onesports.intelligent.k12.polarlight.domain.entity.course.CourseScheduleDetail;
import com.onesports.intelligent.k12.polarlight.domain.entity.course.SpellingClassPeriod;
import com.onesports.intelligent.k12.polarlight.domain.entity.market.Orders;
import com.onesports.intelligent.k12.polarlight.domain.entity.market.ProductSellPriceRel;
import com.onesports.intelligent.k12.polarlight.domain.entity.organization.Organization;
import com.onesports.intelligent.k12.polarlight.domain.entity.student.StudentAttendance;
import com.onesports.intelligent.k12.polarlight.domain.entity.sys.Config;
import com.onesports.intelligent.k12.polarlight.domain.entity.sys.WxUserPurse;
import com.onesports.intelligent.k12.polarlight.domain.vo.cls.SellClaRuleInfoVO;
import com.onesports.intelligent.k12.polarlight.domain.vo.course.ClassMinNumVO;
import com.onesports.intelligent.k12.polarlight.domain.vo.course.ClassScheduleVO;
import com.onesports.intelligent.k12.polarlight.domain.vo.course.ClassStuScheduleDetailVO;
import com.onesports.intelligent.k12.polarlight.enums.ClassInfoStatusEnum;
import com.onesports.intelligent.k12.polarlight.enums.MessageTypeEnum;
import com.onesports.intelligent.k12.polarlight.enums.ScheduleDetailStatusEnum;
import com.onesports.intelligent.k12.polarlight.enums.SysConfigTypeEnum;
import com.onesports.intelligent.k12.polarlight.mapper.course.ClassInfoMapper;
import com.onesports.intelligent.k12.polarlight.mapper.market.ProductSellPriceRelMapper;
import com.onesports.intelligent.k12.polarlight.service.course.ClassStudentScheduleDetailService;
import com.onesports.intelligent.k12.polarlight.service.course.CourseScheduleDetailService;
import com.onesports.intelligent.k12.polarlight.service.course.SpellingClassPeriodService;
import com.onesports.intelligent.k12.polarlight.service.grade.ClassInfoService;
import com.onesports.intelligent.k12.polarlight.service.index.HomePageService;
import com.onesports.intelligent.k12.polarlight.service.operation.OrganizationService;
import com.onesports.intelligent.k12.polarlight.service.order.OrderService;
import com.onesports.intelligent.k12.polarlight.service.student.ClassStudentService;
import com.onesports.intelligent.k12.polarlight.service.student.StudentAttendanceService;
import com.onesports.intelligent.k12.polarlight.service.sys.ConfigService;
import com.onesports.intelligent.k12.polarlight.service.sys.MessageNotifyService;
import com.onesports.intelligent.k12.polarlight.service.wx.SpellClassService;
import com.onesports.intelligent.k12.polarlight.service.wx.WxUserPurseService;
import com.onesports.intelligent.k12.polarlight.util.RedisUtils;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.units.qual.A;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 定时任务服务
 *
 * @author clw
 * @date 2022-07-19 11:16:10
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class ScheduleJobService {

    private final ConfigService configService;
    private final ClassInfoMapper classInfoMapper;
    private final ClassInfoService classInfoService;
    private final ClassStudentService classStudentService;
    private final SpellingClassPeriodService spellingClassPeriodService;


    private final CourseScheduleDetailService scheduleDetailService;
    private final OrderService orderService;
    private final CourseScheduleDetailService courseScheduleDetailService;
    private final HomePageService homePageService;
    private final ProductSellPriceRelMapper productSellPriceRelMapper;
    private final OrganizationService organizationService;
    private final MessageNotifyService messageNotifyService;
    private final WxUserPurseService wxUserPurseService;
    private final SpellClassService spellClassService;
    private final StudentAttendanceService studentAttendanceService;
    private final ClassStudentScheduleDetailService classStudentScheduleDetailService;

    private static final Integer MINUTE = 60;
    private final RedisUtils redisUtils;

    /**
     * 定时添加考勤
     */
    @DistributedLock(value = LockConstants.SCHEDULE_CREATE_ATTENDANCE, keys = "#date")
    @Transactional(rollbackFor = Exception.class)
    public void addStudentAttendance(Date date) {
        try {
            String key = "ADD_ATTENDANCE";
            if (redisUtils.exists(key)) {
                return;
            }
            this.redisUtils.set(key, TimeUtils.toText(date, TimeUtils.DATE_FORMAT), 30L);
            log.info("定时任务添加考勤start,当前时间为：{}", TimeUtils.toText(date, TimeUtils.DATE_TIME_FORMAT));
            //获取配置
            Config config = this.configService.getConfig(SysConfigTypeEnum.C.getValue());
            log.info("课表上课前x小时配置信息:{}", config);
            Config continuousConfig = this.configService.getConfig(SysConfigTypeEnum.B.getValue());
            log.info("连续x次解散配置信息:{}", continuousConfig);
            int minute = Integer.parseInt(config.getValue()) * MINUTE;
            Date current = DateUtils.addMinuteToDate(minute, date, DateUtils.YYYY_MM_DD_HH_MM);
            //获取符合条件的时段
            String currentDateStr = TimeUtils.toText(current, TimeUtils.DATE_FORMAT);
            String currentTimeStr = TimeUtils.toText(current, DateUtils.HH_MM);
            log.info("筛选条件：日期：{},当前时段开始时间：{}", currentDateStr, currentTimeStr);
            Date currentDate = TimeUtils.toDate(currentDateStr, TimeUtils.DATE_FORMAT);
            Date currentTime = TimeUtils.toDate(currentTimeStr, DateUtils.HH_MM);

            //获取符合条件的班级(拼班成功、开班中)
            List<String> classStatusList = Arrays.asList(ClassInfoStatusEnum.FINISH_CLASS.getValue(), ClassInfoStatusEnum.OPEN_CLASS.getValue());
            List<ClassInfo> classInfos = this.classInfoMapper.selectList(new LambdaQueryWrapper<ClassInfo>()
                    .in(ClassInfo::getClassStatus, classStatusList));
            log.info("拼班成功、开班中的班级：{}", classInfos);
            if (ParamUtils.isEmpty(classInfos)) {
                return;
            }
            Map<String, ClassInfo> classMap = classInfos.stream().collect(Collectors.toMap(BaseEntity::getId, info -> info));
            List<String> classIdList = classInfos.stream().map(BaseEntity::getId).collect(Collectors.toList());

            //获取班级价格拼班规则信息
            Map<String, SellClaRuleInfoVO> spellClaRuleMap = this.classInfoService.spellClaRuleByClaIds(classIdList)
                    .stream().collect(Collectors.toMap(SellClaRuleInfoVO::getClassId, spell -> spell));
            log.info("班级价格拼班规则信息：{}", spellClaRuleMap);
            //获取班级下学员的时段信息
            List<ClassStuScheduleDetailVO> stuScheduleDetails = this.classStudentService.stuScheduleDetail(classIdList, currentDate, currentTime);
            log.info("班级下学员的时段信息：{}", stuScheduleDetails);
            if (ParamUtils.isEmpty(stuScheduleDetails)) {
                return;
            }
            //获取班级上节课是否是待考勤
            Set<String> preDetailIds = stuScheduleDetails.stream().map(ClassStuScheduleDetailVO::getPreScheduleDetailId).collect(Collectors.toSet());

            List<SpellingClassPeriod> periods = this.spellingClassPeriodService.periodByClassIdAndAttendanceStatus(classIdList,
                    Arrays.asList(ScheduleDetailStatusEnum.WAIT_ATTENDANCE.getValue(), ScheduleDetailStatusEnum.ATTENDANCE_ING.getValue()),
                    new ArrayList<>(preDetailIds));

            Map<String, List<SpellingClassPeriod>> periodMap = new HashMap<>();
            if (ParamUtils.isNotEmpty(periods)){
                periodMap = periods.stream().collect(Collectors.groupingBy(SpellingClassPeriod::getClassInfoId));
            }
            //获取所有课表详情信息
            Map<String, CourseScheduleDetail> scheduleDetailMap = this.scheduleDetailService.list()
                    .stream().collect(Collectors.toMap(BaseEntity::getId, detail -> detail));

            //获取所有家长的钱包信息
            List<WxUserPurse> wxUserPurses = this.wxUserPurseService.list();
            if (ParamUtils.isEmpty(wxUserPurses)) {
                return;
            }
            //处理每个班级够钱的学员的一系列恶心的操作，没眼看（嫌弃）
            //依据班级分组
            Map<String, List<ClassStuScheduleDetailVO>> classStuMap = stuScheduleDetails.stream().collect(Collectors.groupingBy(ClassStuScheduleDetailVO::getClassId));
            Map<String, List<SpellingClassPeriod>> finalPeriodMap = periodMap;
            classStuMap.forEach((classId, stuList) -> {
                log.info("班级下学员的信息：{}", stuList);
                //获取本次课表详情信息
                String scheduleDetailId = stuList.get(0).getScheduleDetailId();
                CourseScheduleDetail scheduleDetail = scheduleDetailMap.get(scheduleDetailId);
                SpellingClassPeriod period = this.spellingClassPeriodService.period(classId, scheduleDetailId);
                //获取班级拼班规则和价格信息
                SellClaRuleInfoVO sellClaRuleInfo = spellClaRuleMap.get(classId);
                //当前班级可添加的学员
                List<StuSpellClassDTO.Stu> canAddStu = new ArrayList<>();
                //不可上课的学员
                List<StuSpellClassDTO.Stu> unStu = new ArrayList<>();
                //需要发送充值提醒的家长信息
                Map<String, List<StuSpellClassDTO.Stu>> noticeMap = new HashMap<>();
                //获取班级信息
                ClassInfo classInfo = classMap.get(classId);
                //学员依据家长分组
                Map<String, List<ClassStuScheduleDetailVO>> userStuMap = stuList.stream().collect(Collectors.groupingBy(ClassStuScheduleDetailVO::getUserId));
                //校验每个家长的余额是否充足
                userStuMap.forEach((userId, userStuList) -> {
                    //转换一下学员
                    List<StuSpellClassDTO.Stu> studentList = userStuList.stream()
                            .map(i -> BeanUtils.copyProperties(i, StuSpellClassDTO.Stu.class)).collect(Collectors.toList());
                    //校验余额
                    List<StuSpellClassDTO.Stu> allStuList = this.spellClassService.checkBalance(userId, sellClaRuleInfo.getPrice(), studentList);
                    //可继续上下一节课的学员
                    canAddStu.addAll(allStuList.stream().filter(f -> f.getIsNotice().equals(Boolean.FALSE)).collect(Collectors.toList()));
                    unStu.addAll(allStuList.stream().filter(f -> f.getIsNotice().equals(Boolean.TRUE)).collect(Collectors.toList()));
                    //需要发送充值提醒的学员
                    List<StuSpellClassDTO.Stu> sendNoticeStuList = allStuList.stream().filter(f -> f.getIsNotice().equals(Boolean.TRUE)).collect(Collectors.toList());
                    if (ParamUtils.isNotEmpty(sendNoticeStuList)) {
                        noticeMap.put(userId, sendNoticeStuList);
                    }
                });
                //校验班级学员上课数量是否满足最低要求
                if (ParamUtils.isNotEmpty(canAddStu) && canAddStu.size() >= sellClaRuleInfo.getMinNum()) {
                    //满足要求可开课：添加学员考勤、冻结金额（创建订单）、更改班级的连续次数、设置课表详情的应到人数、保存班级学员课表
                    //冻结金额（创建订单）
                    this.spellClassService.createOrder(classId, canAddStu, scheduleDetailId, false);
                    //添加学员考勤
                    this.addAttendance(canAddStu, classInfo, scheduleDetail, period);
                    //更改学员下次课的冻结状态
                    this.updateNextScheduleStatus(canAddStu, scheduleDetailId);
                    //更改班级的连续次数
                    classInfo.setConsecutiveNum(0);
                    classInfo.setNoticeStuIds(BizConstants.OPERATION_ORGANIZATION_ID);
                    //保存班级学员课表
                    List<String> classStudentIds = canAddStu.stream().map(StuSpellClassDTO.Stu::getClassStudentId).collect(Collectors.toList());
                    this.spellClassService.stuScheduleDetail(classStudentIds, scheduleDetail, classId);
                } else {
                    //不满足要求：更改班级的连续次数、当前课表作废、校验连续次数是否满足解散班级
                    //更改班级的连续次数
                    classInfo.setConsecutiveNum(classInfo.getConsecutiveNum() + 1);
                    String unStuStr = unStu.stream().map(StuSpellClassDTO.Stu::getStudentId).collect(Collectors.joining(","));
                    classInfo.setNoticeStuIds(unStuStr);
                    //消息提醒
                    log.info("人数不足消息提醒调用========================");
                    this.messageNotifyService.cannotOpenClass(scheduleDetailId, classId, unStuStr, stuList.stream().map(ClassStuScheduleDetailVO::getStudentId).collect(Collectors.joining(",")));
                    period.setLastModifiedBy("定时任务：addStudentAttendance：班级上课数量不满足人数要求，此课表作废");
                    period.setLastModifiedDate(Instant.now());
                    period.setClassAttendanceStatus(ScheduleDetailStatusEnum.CANCEL.getValue());
                    period.setReason("定时任务添加学员考勤：课款不足导致人数不足本次课表作废");
                    if (classInfo.getConsecutiveNum() >= Integer.parseInt(continuousConfig.getValue())) {
                        DissolutionClassDTO dto = new DissolutionClassDTO();
                        dto.setClassId(classId);
                        if (ParamUtils.isEmpty(finalPeriodMap) || (ParamUtils.isNotEmpty(finalPeriodMap) && ParamUtils.isEmpty(finalPeriodMap.get(classId)))){
                            log.info("所有课程已考勤，达到连续次数可以解散班级id，{}", classId);
                            this.classInfoService.dissolutionClass(dto);
                        }
                    }
                }
                //数据保存
                log.info("添加考勤更改班级数据，{}", classInfo);
                this.classInfoService.updateById(classInfo);
                this.scheduleDetailService.updateById(scheduleDetail);
                this.spellingClassPeriodService.updateById(period);

                if (ParamUtils.isNotEmpty(noticeMap)) {
                    log.info("课款不足消息提醒调用==========课款不足学员：==============，{}", noticeMap);
                    noticeMap.forEach((userId, noticeStu) -> {
                        List<String> stuIds = noticeStu.stream().map(StuSpellClassDTO.Stu::getStudentId).collect(Collectors.toList());
                        this.messageNotifyService.noEnoughMoney(userId, classInfo.getCoachId(), stuIds, scheduleDetailId, classInfo.getCourseId());
                    });
                }
            });
            log.info("定时任务添加考勤end");
        } catch (Exception e) {
            log.error("定时任务添加考勤异常，{}", e.getMessage());
        }

    }


    /**
     * 更改学员下次课的冻结状态
     *
     * @param canAddStu        可添加考勤的学员
     * @param scheduleDetailId 下节课表详情id
     */
    private void updateNextScheduleStatus(List<StuSpellClassDTO.Stu> canAddStu, String scheduleDetailId) {

        List<String> classStudentIds = canAddStu.stream().map(StuSpellClassDTO.Stu::getClassStudentId).collect(Collectors.toList());
        this.classStudentScheduleDetailService.update(new UpdateWrapper<ClassStudentScheduleDetail>()
                .in("class_student_id", classStudentIds)
                .eq("next_schedule_detail_id", scheduleDetailId)
                .set("is_freeze", true));


    }

    private void addAttendance(List<StuSpellClassDTO.Stu> stuList, ClassInfo classInfo,
                               CourseScheduleDetail scheduleDetail, SpellingClassPeriod period) {
        List<StudentAttendance> attendances = stuList.stream().map(i -> {
            StudentAttendance attendance = BeanUtils.copyProperties(i, StudentAttendance.class);
            attendance.setCoachId(classInfo.getCoachId());
            return attendance;
        }).collect(Collectors.toList());

        this.studentAttendanceService.saveBatch(attendances);

        //设置课表详情的应到人数
        scheduleDetail.setDueNum(scheduleDetail.getDueNum() + attendances.size());
        period.setDueNum(period.getDueNum() + attendances.size());
    }


    /**
     * 课程到期
     */
    @Transactional(rollbackFor = Exception.class)
    @DistributedLock(value = LockConstants.COURSE_EXPIRE)
    public void courseExpires() {


        Instant date = Instant.now();
        //查询所有班级
        List<ClassInfo> classInfos = classInfoService.list();
        Map<String, ClassInfo> classInfoMap = classInfos.stream().collect(Collectors.toMap(ClassInfo::getId, c -> c));
        //查询所有班级学生
        LambdaQueryWrapper<ClassStudent> classStudentWrapper = new LambdaQueryWrapper<>();
        classStudentWrapper.eq(ClassStudent::getActive, Boolean.TRUE);
        List<ClassStudent> classStudentList = classStudentService.list(classStudentWrapper);
        //分组
        Map<String, List<ClassStudent>> classStudentMap = classStudentList.stream().collect(Collectors.groupingBy(ClassStudent::getClassInfoId));
        //当前时间
        String now = getLocalDateTimeStr();
        LocalDateTime localDateTimeNow = DateUtils.parseLocalDateTime(now, DateUtils.YYYY_MM_DD_HH_MM);
        //查询班级状态为 0、1、3
        List<ClassScheduleVO> classList = classInfoService.findClassSchedule();
        if (ParamUtils.isNotEmpty(classList)) {
            for (ClassScheduleVO classScheduleVO : classList) {

                String endDate = TimeUtils.toText(classScheduleVO.getEndDate());
                String endTime = TimeUtils.toText(classScheduleVO.getEndTime(), HH_MM);
                StringJoiner result = new StringJoiner("");
                String endDateTime = result.add(endDate).add(" ").add(endTime).toString();
                LocalDateTime localDateTimeCourse = DateUtils.parseLocalDateTime(endDateTime, YYYY_MM_DD_HH_MM);
                DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
                log.info("时间比较:当前时间:{},课程时间:{},班级Id:{}",df.format(localDateTimeNow),df.format(localDateTimeCourse),classScheduleVO.getClassInfoId());
                //判断是否相等
                if (localDateTimeNow.isEqual(localDateTimeCourse) || localDateTimeNow.isAfter(localDateTimeCourse)) {
                    log.info("到期结课:当前时间:{},课程时间:{},班级Id:{}",df.format(localDateTimeNow),df.format(localDateTimeCourse),classScheduleVO.getClassInfoId());
                    //修改班级状态和班级学生状态
                    ClassInfo classInfo = classInfoMap.get(classScheduleVO.getClassInfoId());
                    LambdaQueryWrapper<SpellingClassPeriod> periodWrap = new LambdaQueryWrapper<>();
                    periodWrap.eq(SpellingClassPeriod::getClassInfoId,classInfo.getId());
                    periodWrap.eq(SpellingClassPeriod::getClassAttendanceStatus,ScheduleDetailStatusEnum.WAIT_ATTENDANCE.getValue());
                    periodWrap.eq(SpellingClassPeriod::getClassAttendanceStatus,ScheduleDetailStatusEnum.ATTENDANCE_ING.getValue());
                    List<SpellingClassPeriod> spellingClassPeriodList = spellingClassPeriodService.list(periodWrap);
                    if(ParamUtils.isEmpty(spellingClassPeriodList)){
                        if (ParamUtils.isNotEmpty(classInfo)) {
                            log.info("更新班级：班级Id:{}",classInfo.getId());
                            classInfo.setClassStatus(ClassInfoStatusEnum.DISSOLVE_CLASS_EXPIRED.getValue());
                            classInfo.setEndTime(Date.from(date));
                            classInfo.setLastModifiedBy("定时任务：courseExpires");
                            classInfo.setLastModifiedDate(date);
                            classInfoService.updateById(classInfo);
                        }
                        //修改班级学员信息
                        List<ClassStudent> classStudents = classStudentMap.get(classScheduleVO.getClassInfoId());
                        if (ParamUtils.isNotEmpty(classStudents)) {
                            log.info("根据班级Id更新班级学员：班级Id:{}",classInfo.getId());
                            for (ClassStudent classStudent : classStudents) {
                                classStudent.setExitTime(new Date());
                                classStudent.setActive(Boolean.FALSE);
                                classStudent.setLastModifiedBy("定时任务：courseExpires");
                                classStudent.setLastModifiedDate(date);
                                classStudentService.updateById(classStudent);

                            }
                        }

                        //释放教练
                        classInfoService.freedCoach(classInfo);
                        //班级解散（到期结课）提醒
                        messageNotifyService.classDissolvedDueDate(classInfo.getId(), MessageTypeEnum.CLASS_DISSOLVED_DUE_DATE);

                    }

                }
            }
        }

    }

    /**
     * 满足最低人数要求
     */
    @Transactional(rollbackFor = Exception.class)
    @DistributedLock(value = LockConstants.MIN_NUM)
    public void minNum() {
        //当前时间
        String nowDate = getLocalDateTimeStr();
        LocalDateTime localDateTime = parseLocalDateTime(nowDate, DateUtils.YYYY_MM_DD_HH_MM);
        log.info("当前时间:{},{}:", nowDate, localDateTime);
        Instant now = Instant.now();
        Date date = Date.from(now);
        //查询配置信息
        Config config = configService.getConfig(SysConfigTypeEnum.A.getValue());
        //查询所有班级
        List<ClassInfo> classInfoList = classInfoService.list();
        Map<String, ClassInfo> classInfoMap = classInfoList.stream().collect(Collectors.toMap(ClassInfo::getId, c -> c));
        //查询所有商品拼班类型表信息
        LambdaQueryWrapper<ProductSellPriceRel> pspWra = new LambdaQueryWrapper<>();
        pspWra.eq(ProductSellPriceRel::getDeleted, Boolean.FALSE);
        List<ProductSellPriceRel> productSellPriceRelList = productSellPriceRelMapper.selectList(pspWra);
        Map<String, ProductSellPriceRel> productSellPriceRelMap = productSellPriceRelList.stream().collect(Collectors.toMap(ProductSellPriceRel::getId, p -> p));
        if (ParamUtils.isNotEmpty(config)) {
            // 获取配置时间
            int minute = Integer.parseInt(config.getValue()) * 60;
            // 查询拼班中的班级信息
            List<ClassMinNumVO> classInfos = classInfoMapper.getSpellClassInfoList();
            if (ParamUtils.isNotEmpty(classInfos)) {
                for (ClassMinNumVO classInfo : classInfos) {
                    // 获取创建班级的时间
                    Date createdDate = classInfo.getCreatedDate();
                    Date ultimateDate = DateUtils.addMinuteToDate(minute, createdDate, DateUtils.YYYY_MM_DD_HH_MM);
                    log.info("创建班级时间加上配置时间:{}", ultimateDate);
                    if (localDateTime.isEqual(date2LocalDateTime(ultimateDate))
                            || localDateTime.isAfter(date2LocalDateTime(ultimateDate))) {

                        ClassInfo ci = classInfoMap.get(classInfo.getClassInfoId());
                        if (ParamUtils.isNotEmpty(ci)) {
                            ProductSellPriceRel productSellPriceRel = productSellPriceRelMap.get(classInfo.getProductSellPriceRelId());
                            if (ParamUtils.isNotEmpty(productSellPriceRel)) {
                                // 最低人数
                                int minNum = productSellPriceRel.getMinNum();
                                // 已拼学员数
                                int useStudentNum = classInfo.getUseStudentNum();
                                if (useStudentNum >= minNum) {

                                    //获取当前班级学员数量(是之前添加到班级的学员)
                                    List<ClassStudent> classStudents = this.classStudentService.currentStu(ci.getId(), null);
                                    //最新的课表详情id
                                    String scheduleDetailId = spellClassService.getScheduleDetailId(date, ci);
                                    if (ParamUtils.isNotEmpty(scheduleDetailId)) {
                                        CourseScheduleDetail courseScheduleDetail = this.courseScheduleDetailService.getById(scheduleDetailId);
                                        SpellingClassPeriod period = this.spellingClassPeriodService.period(ci.getId(), courseScheduleDetail.getId());
                                        //拼班成功的操作
                                        spellClassService.spellSuccessOpt("定时任务：minNum", now, classStudents, ci, courseScheduleDetail, date, period);

                                    }
                                    // 校区表拼班数量加一
                                    //organizationCampusService.addCampusSpellNum(classInfo.getCampusId());
                                } else {
                                    DissolutionClassDTO dto = new DissolutionClassDTO();
                                    dto.setClassId(classInfo.getClassInfoId());
                                    // 不满足最低人数要求 解散班级
                                    dto.setOptBy(ci.getLastModifiedBy());
                                    dto.setOptDate(ci.getLastModifiedDate());
                                    classInfoService.dissolutionClass(dto);
                                    // 班级解散：人数不足
                                    //messageNotifyService.classDissolvedDueDate(classInfo.getClassInfoId(), MessageTypeEnum.CLASS_DISSOLVED_NO_ENOUGH);
                                    messageNotifyService.noEnoughPeople(classInfo.getClassInfoId());
                                }
                            }

                        }

                    }
                }
            }
        }
    }

    /**
     * 当前时间和course_date + end_time 判断是否过期
     */
    public void spellingClassPeriod() {
        //当前时间
        String now = getLocalDateTimeStr();
        LocalDateTime localDateTimeNow = DateUtils.parseLocalDateTime(now, YYYY_MM_DD_HH_MM);
        //查询所有班级拼班时段表信息
        LambdaQueryWrapper<SpellingClassPeriod> scpWra = new LambdaQueryWrapper<>();
        scpWra.eq(SpellingClassPeriod::getStatus, NOT_EXPIRED);
        List<SpellingClassPeriod> spellingClassPeriodList = spellingClassPeriodService.list(scpWra);
        if (ParamUtils.isNotEmpty(spellingClassPeriodList)) {
            for (SpellingClassPeriod spellingClassPeriod : spellingClassPeriodList) {
                String courseDate = TimeUtils.toText(spellingClassPeriod.getCourseDate());
                String endTime = TimeUtils.toText(spellingClassPeriod.getEndTime(), HH_MM);
                StringJoiner result = new StringJoiner("");
                String courseDateTime = result.add(courseDate).add(" ").add(endTime).toString();
                LocalDateTime localDateTimeCourse = DateUtils.parseLocalDateTime(courseDateTime, YYYY_MM_DD_HH_MM);
                log.info("当前时间:{},数据查询出来的时间:{}", localDateTimeNow, localDateTimeCourse);
                //当前时间和course_date + end_time 判断是否过期
                if (localDateTimeNow.isEqual(localDateTimeCourse) || localDateTimeNow.isAfter(localDateTimeCourse)) {
                    spellingClassPeriod.setStatus(Constants.EXPIRED);
                    //先不改这个
//                    spellingClassPeriod.setClassAttendanceStatus(ScheduleDetailStatusEnum.CANCEL.getValue());
                    spellingClassPeriod.setLastModifiedBy("定时任务：spellingClassPeriod");
                    spellingClassPeriod.setLastModifiedDate(Instant.now());
                    spellingClassPeriodService.saveOrUpdate(spellingClassPeriod);
                }

            }
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void expireFinishClass(Date date) {

        String key = "EXPIRE_FINISH_CLASS";
        if (redisUtils.exists(key)) {
            return;
        }
        this.redisUtils.set(key, TimeUtils.toText(date, TimeUtils.DATE_FORMAT), 30L);

        String currentDate = TimeUtils.toText(date, TimeUtils.DATE_FORMAT);
        String currentTime = TimeUtils.toText(date, DateUtils.HH_MM);
        //获取过期的课表
        List<SpellingClassPeriod> list = this.spellingClassPeriodService.expireScheduleDetail(currentDate, currentTime, ScheduleDetailStatusEnum.ATTENDANCE_ING.getValue());
        log.info("过期课表详情：{}", list);
        if (ParamUtils.isEmpty(list)) {
            return;
        }

        list.forEach(i -> {
            i.setLastModifiedBy("定时任务：expireFinishClass");
            i.setLastModifiedDate(Instant.now());
            i.setClassAttendanceStatus(ScheduleDetailStatusEnum.ATTENDANCE_ED.getValue());
        });

        this.spellingClassPeriodService.updateBatchById(list);

        //分佣
        Set<String> classIds = list.stream().map(SpellingClassPeriod::getClassInfoId).collect(Collectors.toSet());
        classIds.forEach(this.orderService::commissionFirst);

        //消息提醒
        this.messageNotice(classIds);

    }

    private void messageNotice(Set<String> classIds) {
        List<String> ids = new ArrayList<>(classIds);
        Map<String, ClassInfo> classMap = this.classInfoMapper.selectBatchIds(ids).stream().collect(Collectors.toMap(BaseEntity::getId, cla -> cla));

        //获取班级价格拼班规则信息
        Map<String, SellClaRuleInfoVO> spellClaRuleMap = this.classInfoService.spellClaRuleByClaIds(ids)
                .stream().collect(Collectors.toMap(SellClaRuleInfoVO::getClassId, spell -> spell));

        //获取班级下学员的时段信息
//        Date nextDate = TimeUtils.addDay(date, 1);
        List<ClassStuScheduleDetailVO> stuScheduleDetails = this.classStudentService.stuScheduleDetail(ids, null, null);
        log.info("班级下学员的时段信息：{}", stuScheduleDetails);
        if (ParamUtils.isEmpty(stuScheduleDetails)) {
            return;
        }
        Map<String, List<ClassStuScheduleDetailVO>> classStuMap = stuScheduleDetails.stream().collect(Collectors.groupingBy(ClassStuScheduleDetailVO::getClassId));
        ids.forEach(i->{
            //班级拼班详情数据
            SellClaRuleInfoVO sellClaRuleInfo = spellClaRuleMap.get(i);
            List<ClassStuScheduleDetailVO> classStuScheduleDetail = classStuMap.get(i);
            List<ClassStuScheduleDetailVO> list = classStuScheduleDetail.stream()
                    .filter(f -> f.getAttendanceStatus().equals(ScheduleDetailStatusEnum.WAIT_ATTENDANCE.getValue()))
                    .collect(Collectors.toList());
            if (ParamUtils.isNotEmpty(list)){
                List<ClassStuScheduleDetailVO> data = list.stream()
                        .sorted(Comparator.comparing(ClassStuScheduleDetailVO::getCourseDate))
                        .collect(Collectors.toList());
                Map<String, List<ClassStuScheduleDetailVO>> scheduleDetailMap = data.stream().collect(Collectors.groupingBy(ClassStuScheduleDetailVO::getUserId));
                //需要发送充值提醒的家长信息
                Map<String, List<StuSpellClassDTO.Stu>> noticeMap = new HashMap<>();
                //获取班级信息
                ClassInfo classInfo = classMap.get(i);
                scheduleDetailMap.forEach((userId, stuList) -> {
                    //转换一下学员
                    List<StuSpellClassDTO.Stu> studentList = stuList.stream()
                            .map(j -> BeanUtils.copyProperties(j, StuSpellClassDTO.Stu.class)).collect(Collectors.toList());
                    //校验余额
                    List<StuSpellClassDTO.Stu> allStuList = this.spellClassService.checkBalance(userId, sellClaRuleInfo.getPrice(), studentList);
                    //需要发送充值提醒的学员
                    List<StuSpellClassDTO.Stu> sendNoticeStuList = allStuList.stream().filter(f -> f.getIsNotice().equals(Boolean.TRUE)).collect(Collectors.toList());
                    if (ParamUtils.isNotEmpty(sendNoticeStuList)) {
                        noticeMap.put(userId, sendNoticeStuList);
                    }
                });
                if (ParamUtils.isNotEmpty(noticeMap)) {
                    log.info("课款不足消息提醒调用==========课款不足学员：==============，{}", noticeMap);
                    noticeMap.forEach((userId, noticeStu) -> {
                        List<String> stuIds = noticeStu.stream().map(StuSpellClassDTO.Stu::getStudentId).collect(Collectors.toList());
                        String scheduleDetailId = noticeStu.get(0).getScheduleDetailId();
                        this.messageNotifyService.noEnoughMoney(userId, classInfo.getCoachId(), stuIds, scheduleDetailId, classInfo.getCourseId());
                    });
                }
            }
        });
    }


    /**
     * 首页看板
     *
     * @param date 当前日期
     */
    @DistributedLock(value = LockConstants.HOME_PAGE, keys = "#date")
    @Transactional(rollbackFor = Exception.class)
    public void homePage(Date date) {
        String key = "HOME_PAGE";
        if (redisUtils.exists(key)) {
            return;
        }
        this.redisUtils.set(key, TimeUtils.toText(date, TimeUtils.DATE_FORMAT), 30L);
        this.homePageService.saveData(date);
    }

    /**
     * 定时器调用到过期时间停用机构
     */
    @DistributedLock(value = LockConstants.LOCK_ORGANIZATION_EXPIRE_STOP)
    public void batchUpdateStatus() {
        Date currentDate = new Date();
        //依据当前时间获取到期的机构
        LambdaQueryWrapper<Organization> apply = new LambdaQueryWrapper<Organization>()
                .apply("to_char(occupancy_period,'yyyy-MM-dd') <= {0}", TimeUtils.toText(currentDate, "yyyy-MM-dd"));
        List<Organization> organizations = organizationService.list(apply);
        if (ParamUtils.isNotEmpty(organizations)) {
            List<String> organizationIds = organizations.stream().map(Organization::getId).collect(Collectors.toList());
            OrgDisabledDTO dto = new OrgDisabledDTO();
            dto.setOrganizationIds(organizationIds);
            dto.setStatus(Boolean.FALSE);
            organizationService.batchUpdateStatus(dto);
        }
    }

    @DistributedLock(value = LockConstants.LOCK_BATCH_CONFIRM_ORDER)
    public void batchConfirmOrder() {
        String key = "BATCH_CONFIRM_ORDER";
        if (redisUtils.exists(key)) {
            return;
        }
        this.redisUtils.set(key, key, 30L);
        String value = configService.getValueByType(SysConfigTypeEnum.D);
        if (StringUtils.isBlank(value)) {
            return;
        }
        int hour = Integer.parseInt(value);
        Date confirmTime = DateUtils.addHours(new Date(), -hour);
        List<Orders> ordersList = orderService.getAttendanceOrders(confirmTime);
        if (CollectionUtils.isEmpty(ordersList)) {
            return;
        }
        ordersList.forEach(order -> {
            try {
                orderService.confirm(order,true);
            } catch (Exception e) {
                log.error("订单("+order.getOrderNo()+")确认支付失败：" + e.getMessage());
            }
        });
    }

    /**
     * function:  上课提醒
     *
     * @author WangCX
     * @since 2022/8/2 18:42
     */
    public void classRemind() {
        messageNotifyService.classRemindNotification();
    }
}
